﻿#region Header

/*
Behavioral Rating of Dancing Human Crowds based on Motion Patterns
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using paravili.Services;
using Sebarf.Services.Interfaces;

#endregion

namespace paravili.Steps {
	/// <summary>
	/// Description of the class
	/// </summary>
	public class FindMovementBetweenClusters3 :
		ProcessStepConverterWithMeasurement<IList<IList<ColoredPoint>>, IList<Cluster>> {
		#region Public Properties

		[ConfigurableNumericValue(Name = "WeightOfDistance", RangeFrom = 1, RangeTo = 1000)]
		public int WeightOfDistance { get; set; }

		[ConfigurableNumericValue(Name = "WeightOfTime", RangeFrom = 1, RangeTo = 1000)]
		public int WeightOfTime { get; set; }

		[ConfigurableNumericValue(Name = "WeightOfColour", RangeFrom = 1, RangeTo = 1000)]
		public int WeightOfColour { get; set; }

		[ConfigurableNumericValue(Name = "WeightConvexHullSize", RangeFrom = 1, RangeTo = 1000)]
		public int WeightConvexHullSize { get; set; }

		[ConfigurableNumericValue(Name = "PossibleMatchingPointsDifferentsPercent", RangeFrom = 1, RangeTo = 100)]
		public int PossibleMatchingPointsDifferentsPercent { get; set; }

		[ConfigurableNumericValue(Name = "MaxFramesBetweenTwoCluster", RangeFrom = 1, RangeTo = 10)]
		public int MaxFramesBetweenTwoCluster { get; set; }

		[ServiceRequest]
		public IResetHandlerService ResetHandlerService {
			set { value.Reset += OnReset; }
		}

		#endregion

		#region Public Methods

		public FindMovementBetweenClusters3() {
			PossibleMatchingPointsDifferentsPercent = 10;
			WeightOfDistance = 50;
			WeightOfTime = 500;
			WeightOfColour = 50;
			WeightConvexHullSize = 500;
			MaxFramesBetweenTwoCluster = 5;
			m_rootCluster = new Cluster {
				ColorX = 500,
				ColorY = 500,
				ColorZ = 500,
				HulPerimeterSize = 200,
				Time = 0,
				AverageYValue = 250,
				AverageXValue = 250
			};
			m_referenceClusters.Add(new Cluster {
				ColorX = 500,
				ColorY = 500,
				ColorZ = 500,
				HulPerimeterSize = 400,
				Time = 0,
				AverageYValue = 50,
				AverageXValue = 50
			});
			m_referenceClusters.Add(new Cluster {
				ColorX = 500,
				ColorY = 500,
				ColorZ = 500,
				HulPerimeterSize = 400,
				Time = 0,
				AverageYValue = 250,
				AverageXValue = 50
			});
			m_referenceClusters.Add(new Cluster {
				ColorX = 500,
				ColorY = 500,
				ColorZ = 500,
				HulPerimeterSize = 400,
				Time = 0,
				AverageYValue = 50,
				AverageXValue = 250
			});
			m_referenceClusters.Add(new Cluster {
				ColorX = 1000,
				ColorY = 1000,
				ColorZ = 1000,
				HulPerimeterSize = 400,
				Time = 0,
				AverageYValue = 50,
				AverageXValue = 50
			});
			m_referenceClusters.Add(new Cluster {
				ColorX = 1000,
				ColorY = 1000,
				ColorZ = 1000,
				HulPerimeterSize = 400,
				Time = 0,
				AverageYValue = 250,
				AverageXValue = 50
			});
			m_referenceClusters.Add(new Cluster {
				ColorX = 1000,
				ColorY = 1000,
				ColorZ = 1000,
				HulPerimeterSize = 400,
				Time = 0,
				AverageYValue = 50,
				AverageXValue = 250
			});
		}

		#endregion

		#region Private Methods

		private readonly List<Cluster> m_referenceClusters = new List<Cluster>();
		private readonly Cluster m_rootCluster;
		private SortedList<Double, Cluster> m_distanceToRootCluster = new SortedList<double, Cluster>();
		private double m_maxColor;
		private double m_maxDistance;
		private double m_maxSize;
		private double m_maxTime;
		private double m_minColor;
		private double m_minDistance;
		private double m_minSize;
		private double m_minTime;

		#endregion

		#region Private Fields

		private readonly List<Cluster> m_oldCluster = new List<Cluster>();

		#endregion

		private int m_time;

		protected override IList<Cluster> Convert(IList<IList<ColoredPoint>> toProcess) {
			var clusterToHandle = new List<Cluster>();
			clusterToHandle.AddRange(m_oldCluster);
			m_oldCluster.Clear();
			m_time++;
			if (toProcess != null) {
				// prepare data
				foreach (var cluster in toProcess) {
					double xTotal = 0;
					double yTotal = 0;
					double maxX = 0;
					double minX = 0;
					double maxY = 0;
					double minY = 0;
					double colorX = 0;
					double colorY = 0;
					double colorZ = 0;
					int points = cluster.Count;
					foreach (ColoredPoint p in cluster) {
						int x = p.P.X;
						int y = p.P.Y;
						colorX += p.Color.X;
						colorY += p.Color.Y;
						colorZ += p.Color.Z;
						xTotal += x;
						yTotal += y;
						if (maxX < x) {
							maxX = x;
						}
						else if (minX > x) {
							minX = x;
						}
						if (maxY < y) {
							maxY = y;
						}
						else if (minY > y) {
							minY = y;
						}
					}
					double distance = Math.Abs(maxX - minX) * Math.Abs(maxY - minY);
					var toAdd = new Cluster {
						Points = cluster,
						AverageXValue = xTotal / points,
						// cluster.Average (c => c.P.X),
						AverageYValue = yTotal / points,
						//cluster.Average (c => c.P.Y),
						Time = m_time,
						HulPerimeterSize = distance,
						//ConvexHul = Algorithm.Convexhull.convexhull(cluster.Select(c => c.P).ToArray()),
						ColorX = colorX / points,
						//cluster.Average(c => c.Color.X),
						ColorY = colorY / points,
						//cluster.Average(c => c.Color.Y),
						ColorZ = colorZ / points //cluster.Average(c => c.Color.Z)
					};
					if (Math.Abs(toAdd.Time - m_time) > MaxFramesBetweenTwoCluster) {
						continue;
					}
					clusterToHandle.Add(toAdd);
				}
			}

			var distanceToRefClusters = new Dictionary<Cluster, double>[m_referenceClusters.Count];
			for (int i = 0; i < m_referenceClusters.Count; i++) {
				distanceToRefClusters[i] = new Dictionary<Cluster, double>();
			}
			// rember found clusters
			IList<Cluster> toReturn = new List<Cluster>();
			if (clusterToHandle.Count == 0) {
				return toReturn;
			}
			var distanceToRootCluster = ((from p in clusterToHandle
										  select
											new {
												DistanceToClusert = MatchFunction(p, m_rootCluster),
												Cluster = p,
												OtherClusters =
										(from c in m_referenceClusters
										 select new { RefCluster = c, Distance = MatchFunction(c, p) }).ToArray()
											}).OrderBy(
				x => x.DistanceToClusert)).ToList();

			while (distanceToRootCluster.Count > 0) {
				var first = distanceToRootCluster.First();
				distanceToRootCluster.RemoveAt(0);
				var candidates =
					distanceToRootCluster.TakeWhile(
						c =>
						Math.Abs(c.DistanceToClusert -
								  (first.DistanceToClusert * (1.0 + PossibleMatchingPointsDifferentsPercent / 100.0))) > 0).ToArray();
				if (candidates.Length == 1) {
					candidates[0].Cluster.PreviousCluster = first.Cluster;
					toReturn.Add(candidates[0].Cluster);

					distanceToRootCluster.RemoveAt(distanceToRootCluster.IndexOf(candidates[0]));
					continue;
				}
				else if (candidates.Length == 0) {
					m_oldCluster.Add(first.Cluster);
					continue;
				}
				else {
					var bestSolution = (from p in candidates
										select
											new {
												Original = p,
												p.Cluster,
												Index = -1,
												ViolationPoints =
										CalcDifferent(p.OtherClusters.Select(y => y.Distance).ToArray(),
													   first.OtherClusters.Select(y => y.Distance).ToArray())
											}).OrderBy(
						c => c.ViolationPoints).FirstOrDefault();
					;

					//                p.OtherClusters.Select(
					//                c => first.OtherClusters[].Distance / p.OtherClusters[index].Distance).Sum(x => x)
					//                    })
					//
					if (bestSolution == null) {
						bestSolution.Cluster.PreviousCluster = first.Cluster;
						toReturn.Add(bestSolution.Cluster);
						distanceToRootCluster.RemoveAt(distanceToRootCluster.IndexOf(bestSolution.Original));
					}
					else {
						m_oldCluster.Add(first.Cluster);
					}
				}

				//- (first.OtherClusters[index].Distance) .Distance * (this.PossibleMatchingPointsDifferentsPercent / 100.0))
				//- (first.OtherClusters[index].Distance) .Distance * (this.PossibleMatchingPointsDifferentsPercent / 100.0))

				//distanceToRefClusters.TakeWhile(c=>c.)	
			}

			//foreach (var entry in m_distanceToRootCluster) {
			//	entry
			//}

			return toReturn;
		}

		private double CalcDifferent(double[] first, double[] last) {
			double result = 0;
			for (int i = 0; i < first.Length; i++) {
				result += first[i] / last[i];
			}
			return result;
		}

		private void OnReset(Object sender, EventArgs args) {
			m_oldCluster.Clear();
		}

		private double MatchFunction(Cluster first, Cluster last) {
			double matchingPoints = 0;
			double distance =
				Math.Pow(
					Math.Pow(
						Math.Pow(first.AverageXValue - last.AverageXValue, 2) + Math.Pow(first.AverageYValue - last.AverageYValue, 2),
						0.5), 2);
			m_minDistance = Math.Min(m_minDistance, distance);
			m_maxDistance = Math.Max(m_maxDistance, distance);

			matchingPoints -= (distance / ReturnOneIfNull(m_maxDistance - m_minDistance) * WeightOfDistance);

			int timeDiff = Math.Abs(first.Time - last.Time);
			m_minTime = Math.Min(m_minTime, timeDiff);
			m_maxTime = Math.Max(m_maxTime, timeDiff);
			matchingPoints -= (timeDiff - 1) / ReturnOneIfNull(m_maxTime - m_minTime) * WeightOfTime;

			double colorDifferenceDifference =
				Math.Pow(
					Math.Pow(first.ColorY - last.ColorY, 2) + Math.Pow(first.ColorX - last.ColorX, 2) +
					Math.Pow(first.ColorZ - last.ColorZ, 2), 0.5);

			m_minColor = Math.Min(m_minColor, colorDifferenceDifference);
			m_maxColor = Math.Max(m_maxColor, colorDifferenceDifference);
			matchingPoints -= (colorDifferenceDifference) / ReturnOneIfNull(m_maxColor - m_minColor) * WeightOfColour;

			double diffConvexHullSize = Math.Abs(first.HulPerimeterSize - last.HulPerimeterSize);
			m_minSize = Math.Min(m_minSize, diffConvexHullSize);
			m_maxSize = Math.Max(m_maxSize, diffConvexHullSize);
			matchingPoints -= ((diffConvexHullSize) / ReturnOneIfNull(m_maxSize - m_minSize)) * WeightConvexHullSize;
			return matchingPoints;
		}

		private double ReturnOneIfNull(double d) {
			if (d == 0) {
				return 1;
			}
			return d;
		}

		//private IList<KeyValuePair<Point, Point>> FindMovement() {
		//    if (m_clusters.Count < 2) {
		//        return null;
		//    }
		//    // remove to old clusters
		//    var maxTime = MaxFramesBetweenTwoCluster;
		//    m_clusters.RemoveAll(c => m_time - c.Time > maxTime);

		//    // rember found clusters
		//    IList<KeyValuePair<Point, Point>> toReturn = new List<KeyValuePair<Point, Point>>();

		//    Cluster firstCluster;
		//    Cluster lastCluster;

		//    var candidates = new List<Cluster>(m_clusters);
		//    var usedClusters = new List<Cluster>();
		//    foreach (var cluster in m_clusters) {
		//        if (usedClusters.Contains(cluster)) {
		//            continue;
		//        }
		//        firstCluster = cluster;
		//        var sortedCluster = candidates.Select(c => new { Match = MatchFunction(firstCluster, c), Cluster = c });
		//        var result = sortedCluster.OrderBy(c => c.Match).LastOrDefault();
		//        if (result.Match == Double.MinValue) {
		//            // no match found
		//        }
		//        else {
		//            lastCluster = result.Cluster;
		//            toReturn.Add(new KeyValuePair<Point, Point>(new Point((int)result.Cluster.X, (int)result.Cluster.Y), new Point((int)firstCluster.X, (int)firstCluster.Y)));
		//            usedClusters.Add(lastCluster);
		//            usedClusters.Add(firstCluster);
		//            candidates.Remove(lastCluster);
		//            candidates.Remove(firstCluster);
		//        }
		//    }
		//    foreach (var cluster in usedClusters) {
		//        m_clusters.Remove(cluster);
		//    }
		//    StatisticValuesProviderService.SetValueToShow("Movements.Found", toReturn.Count);
		//    return toReturn;
		//}
	}
}